package com.gitee.wsl.time.calendar.data

import com.gitee.wsl.time.YearMonth
import com.gitee.wsl.time.atStartOfMonth
import com.gitee.wsl.time.calendar.Calendar
import com.gitee.wsl.time.calendar.bean.CalendarDay
import com.gitee.wsl.time.calendar.bean.CalendarMonth
import com.gitee.wsl.time.calendar.bean.Week
import com.gitee.wsl.time.calendar.style.DayPosition
import com.gitee.wsl.time.calendar.style.OutDateStyle
import com.gitee.wsl.time.numberOfDays
import com.gitee.wsl.time.daysUntil
import com.gitee.wsl.time.firstDayOfWeekBySystem
import com.gitee.wsl.time.minusDays
import com.gitee.wsl.time.monthsUntil
import com.gitee.wsl.time.nextMonth
import com.gitee.wsl.time.plusDays
import com.gitee.wsl.time.plusMonths
import com.gitee.wsl.time.previousMonth
import com.gitee.wsl.time.yearMonth
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlin.time.ExperimentalTime

data class MonthData @OptIn(ExperimentalTime::class) constructor(
    private val month: YearMonth,
    private val inDays: Int,
    private val outDays: Int,
) {
    @OptIn(ExperimentalTime::class)
    private val totalDays = inDays + month.numberOfDays + outDays

    @OptIn(ExperimentalTime::class)
    private val firstDay = month.atStartOfMonth().minusDays(inDays)

    private val rows = (0 until totalDays).chunked(7)

    @OptIn(ExperimentalTime::class)
    private val previousMonth = month.previousMonth

    @OptIn(ExperimentalTime::class)
    private val nextMonth = month.nextMonth

    @OptIn(ExperimentalTime::class)
    val calendarMonth: CalendarMonth =
        CalendarMonth(month, rows.map { week -> Week(week.map { dayOffset -> getDay(dayOffset) }) })

    @OptIn(ExperimentalTime::class)
    private fun getDay(dayOffset: Int): CalendarDay {
        val date = firstDay.plusDays(dayOffset)
        val position = when (date.yearMonth) {
            month -> DayPosition.RangeDate
            previousMonth -> DayPosition.InDate
            nextMonth -> DayPosition.OutDate
            else -> throw IllegalArgumentException("Invalid date: $date in month: $month")
        }
        return CalendarDay(date, position)
    }
}

@OptIn(ExperimentalTime::class)
fun Calendar.Companion.getCalendarMonthData(
    startMonth: YearMonth,
    offset: Int = 0,
    firstDayOfWeek: DayOfWeek = firstDayOfWeekBySystem(),
    outDateStyle: OutDateStyle,
): MonthData {
    val month = startMonth.plusMonths(offset)
    val firstDay = month.atStartOfMonth()
    val inDays = firstDayOfWeek.daysUntil(firstDay.dayOfWeek)
    val outDays = (inDays + month.numberOfDays).let { inAndMonthDays ->
        val endOfRowDays = if (inAndMonthDays % 7 != 0) 7 - (inAndMonthDays % 7) else 0
        val endOfGridDays = if (outDateStyle == OutDateStyle.EndOfRow) {
            0
        } else {
            val weeksInMonth = (inAndMonthDays + endOfRowDays) / 7
            (6 - weeksInMonth) * 7
        }
        return@let endOfRowDays + endOfGridDays
    }
    return MonthData(month, inDays, outDays)
}

@OptIn(ExperimentalTime::class)
fun Calendar.Companion.getHeatMapCalendarMonthData(
    startMonth: YearMonth,
    offset: Int = 0,
    firstDayOfWeek: DayOfWeek,
): MonthData {
    val month = startMonth.plusMonths(offset)
    val firstDay = month.atStartOfMonth()
    val inDays = if (offset == 0) {
        firstDayOfWeek.daysUntil(firstDay.dayOfWeek)
    } else {
        -firstDay.dayOfWeek.daysUntil(firstDayOfWeek)
    }
    val outDays = (inDays + month.numberOfDays).let { inAndMonthDays ->
        if (inAndMonthDays % 7 != 0) 7 - (inAndMonthDays % 7) else 0
    }
    return MonthData(month, inDays, outDays)
}

@OptIn(ExperimentalTime::class)
fun Calendar.Companion.getMonthIndex(startMonth: YearMonth, targetMonth: YearMonth): Int {
    return startMonth.monthsUntil(targetMonth)
}

@OptIn(ExperimentalTime::class)
fun Calendar.Companion.getMonthIndicesCount(startMonth: YearMonth, endMonth: YearMonth): Int {
    // Add one to include the start month itself!
    return getMonthIndex(startMonth, endMonth) + 1
}

fun Calendar.Companion.getDaysOfMonth(date: LocalDate, firstDayOfWeek: DayOfWeek): List<Int> {
    val firstDayOfMonth = LocalDate(date.year, date.month, 1)
    val lastDay = firstDayOfMonth.plus(DatePeriod(months = 1)).minus(DatePeriod(days = 1)).dayOfMonth

    val startDayOffset = (firstDayOfMonth.dayOfWeek.ordinal - firstDayOfWeek.ordinal + 7) % 7
    val days = mutableListOf<Int>().apply {
        addAll(List(startDayOffset) { 0 })
        addAll(1..lastDay)
        while (size % 7 != 0) add(0)
    }
    return days
}